# -*- coding: utf-8 -*-
import os
import sys
import subprocess
from threading import Thread, Event
lambda: "By Zero123"

def GET_BEH_PATHS():
    """ 获取所有加载的BEH包目录 """
    _ROOT_PACK = "/behavior_packs"
    for pathDir in sys.path:
        dirPath = os.path.dirname(pathDir)
        if not os.path.dirname(dirPath).endswith(_ROOT_PACK):
            continue
        yield dirPath

def FIND_BEH_FILE(filePath):
    # type: (str) -> str | None
    """ 从行为包中访问特定文件 如果失败则返回None """
    for behPath in GET_BEH_PATHS():
        newPath = os.path.join(behPath, filePath)
        if os.path.isfile(newPath):
            return os.path.realpath(newPath)
    return None

def _strToU8(oStr=""):
    if type(oStr) == type(u"unicodeStr"):
        return oStr.encode("utf-8")
    return oStr

class MsgModel:
    """ 消息模型 """
    def __init__(self, args=dict()):
        # type: (dict) -> None
        self.text = _strToU8(args.get("text", ""))                # type: str
        """ 消息内容 """
        self.nickname = _strToU8(args.get("nickname", ""))        # type: str
        """ 名称 """
        self.isadmin = bool(args.get("isadmin", 0))               # type: bool
        """ 管理⚪权限 """

    def __str__(self):
        return "<{}>: {}".format(self.nickname, self.text)

class BiliDanmuBot:
    def __init__(self, bindRoomId):
        self._state = False
        self.bindRoomId = bindRoomId
        self._threadStopEvent = None
        self.execPath = FIND_BEH_FILE("QBiliDanmuBot.exe")
        self._listenMap = {}    # type: dict[str, function]
        self._proc = None

    def regFunction(self, name, funcObj):
        """ 注册函数 以便跨进程调用 """
        # type: (str, function) -> None
        self._listenMap[name] = funcObj

    def start(self):
        """ 启用弹幕机 """
        if self._state:
            return
        self._state = True
        self._threadStopEvent = Event()
        self._proc = subprocess.Popen([self.execPath, self.bindRoomId], stdout=subprocess.PIPE)
        Thread(target=self._threadLoop, args=(self._proc, self._threadStopEvent)).start()

    def stop(self):
        """ 关闭弹幕机 """
        if not self._state:
            return
        self._state = False
        self._threadStopEvent.set()
        self._kill()

    def _threadLoop(self, proc, event):
        # type: (subprocess.Popen, Event) -> None
        # 线程循环 接收子进程的输入消息
        from json import loads
        START_KEY = "QIPC_CALL_"
        END_KEY = "_QIPC_ENDL"
        while not event.is_set() and self._proc == proc:
            line = proc.stdout.readline().decode("utf-8").strip()    # type: str
            if not line:
                break  # 如果读取到空字符串，表示子进程可能已经结束
            try:
                if not START_KEY in line or not END_KEY in line:
                    continue
                startPos = line.find(START_KEY)
                endPos = line.rfind(END_KEY)
                joText = line[startPos+len(START_KEY):endPos]
                argsList = loads(joText)    # type: list
                funName = argsList[0]
                if not funName in self._listenMap:
                    continue
                self._eventThreadCall(funName, tuple(argsList[1:]))
            except Exception:
                import traceback
                traceback.print_exc()
        print("{} 进程连接已关闭..".format(proc))

    def _eventThreadCall(self, funcName, args):
        # type: (str, tuple) -> None
        """ 事件循环线程调用(此时并不处于游戏线程 需要进一步处理才能调用API) """
        self._listenMap[funcName](*args)

    def _procIsLive(self):
        # type: () -> None
        """ 进程是否存活 """
        if not self._proc:
            return False
        if self._proc.poll() is None:  
            # 通过状态码判断存活状态
            return True
        self._proc = None
        return False
    
    def _kill(self):
        # type: () -> None
        """ 杀死进程 """
        if self._procIsLive():
            self._proc.kill()
            self._proc = None